import ConnectionStatus from './wfc/client/connectionStatus'
import Vue from 'vue'
import wfc from './wfc/client/wfc'
import EventType from './wfc/client/wfcEvent'
import ConversationType from './wfc/model/conversationType'
import { eq, gt, lt, numberValue } from './wfc/util/longUtil'
import helper from './ui/util/helper'
import convert from './vendor/pinyin'
import GroupType from './wfc/model/groupType'
import {
  imageThumbnail,
  videoDuration,
  videoThumbnail,
} from './ui/util/imageUtil'
import MessageContentMediaType from './wfc/messages/messageContentMediaType'
import Conversation from './wfc/model/conversation'
import MessageContentType from './wfc/messages/messageContentType'
import MessageStatus from './wfc/messages/messageStatus'
import Message from './wfc/messages/message'
import ImageMessageContent from './wfc/messages/imageMessageContent'
import VideoMessageContent from './wfc/messages/videoMessageContent'
import FileMessageContent from './wfc/messages/fileMessageContent'
import Push from 'push.js'
import MessageConfig from './wfc/client/messageConfig'
import PersistFlag from './wfc/messages/persistFlag'
import ForwardType from './ui/main/conversation/message/forward/ForwardType'
import TextMessageContent from './wfc/messages/textMessageContent'
import { currentWindow, ipcRenderer, isElectron, remote } from './platform'
import SearchType from './wfc/model/searchType'
import Config from './config'
import { getItem, setItem } from './ui/util/storageHelper'
import CompositeMessageContent from './wfc/messages/compositeMessageContent'
import { stringValue, longValue } from './wfc/util/longUtil'
import DismissGroupNotification from './wfc/messages/notification/dismissGroupNotification'
import KickoffGroupMemberNotification from './wfc/messages/notification/kickoffGroupMemberNotification'
import QuitGroupNotification from './wfc/messages/notification/quitGroupNotification'
import avenginekitproxy from './wfc/av/engine/avenginekitproxy'
import MediaMessageContent from './wfc/messages/mediaMessageContent'
import UnreadCount from './wfc/model/unreadCount'
import LeaveChannelChatMessageContent from './wfc/messages/leaveChannelChatMessageContent'
import EnterChannelChatMessageContent from './wfc/messages/enterChannelChatMessageContent'
import ArticlesMessageContent from './wfc/messages/articlesMessageContent'
import NullUserInfo from './wfc/model/nullUserInfo'
import NullGroupInfo from './wfc/model/nullGroupInfo'
import IPCEventType from './ipcEventType'
import NullChannelInfo from './wfc/model/NullChannelInfo'
import ModifyGroupSettingNotification from './wfc/messages/notification/modifyGroupSettingNotification'
import { storeToRefs } from 'pinia'
import { pstore } from './pstore'
import CallStartMessageContent from './wfc/av/messages/callStartMessageContent'
import SoundMessageContent from './wfc/messages/soundMessageContent'
import MixMultiMediaTextMessageContent from './wfc/messages/mixMultiMediaTextMessageContent'
import MixFileTextMessageContent from './wfc/messages/mixFileTextMessageContent'

/**
 * 一些说明
 * _开头的字段，是为了UI层展示方便，而打补丁上出去的
 * __开头的字段，仅内部使用
 * _开头的函数，是内部函数
 * 外部不直接更新字段，而是通过提供各种action方法更新
 */
let conversationState
let contactState
let searchState
let pickState
let miscState
let store = {
  debug: true,
  state: {
    conversation: null,
    contact: null,
    search: null,
    pick: null,
    misc: null,
  },

  init(isMainWindow, subWindowLoadDataOptions) {
    console.log('init store')

    const {
      conversationStore,
      contactStore,
      pickStore,
      searchStore,
      miscStore,
    } = storeToRefs(pstore())
    conversationState = conversationStore.value
    contactState = contactStore.value
    searchState = searchStore.value
    pickState = pickStore.value
    miscState = miscStore.value

    store.state.conversation = conversationState
    store.state.contact = contactState
    store.state.search = searchState
    store.state.pick = pickState
    store.state.misc = miscState

    miscState.connectionStatus = wfc.getConnectionStatus()
    wfc.eventEmitter.on(EventType.ConnectionStatusChanged, (status) => {
      console.log('store ConnectionStatusChanged', status)
      contactState.isEnableMesh = wfc.isEnableMesh()
      miscState.connectionStatus = status
      miscState.isCommercialServer = wfc.isCommercialServer()
      miscState.isDisableSyncDraft = wfc.isDisableSyncDraft()
      try {
        if (status === ConnectionStatus.ConnectionStatusConnected) {
          this._loadDefaultData()

          this.updateTray()
        } else if (
          status === ConnectionStatus.ConnectionStatusLogout ||
          status === ConnectionStatus.ConnectionStatusRejected ||
          status === ConnectionStatus.ConnectionStatusSecretKeyMismatch ||
          status === ConnectionStatus.ConnectionStatusKickedOff ||
          status === ConnectionStatus.ConnectionStatusTokenIncorrect
        ) {
          _reset()
          this.updateTray()
        }
      } catch (e) {
        // do nothing
      }
    })

    wfc.eventEmitter.on(EventType.UserInfosUpdate, (userInfos) => {
      console.log(
        'store UserInfosUpdate',
        userInfos,
        miscState.connectionStatus
      )
      this._reloadSingleConversationIfExist(userInfos)
      // TODO optimize
      this._patchCurrentConversationMessages()
      this._loadFriendList()
      this._loadFriendRequest()
      this._loadSelfUserInfo()
      // TODO 其他相关逻辑
    })

    wfc.eventEmitter.on(EventType.SettingUpdate, () => {
      console.log('store SettingUpdate')
      this._loadDefaultConversationList()
      this._loadFavContactList()
      this._loadFavGroupList()
      this.updateTray()
      // 清除远程消息时，WEB SDK会同时触发ConversationInfoUpdate 和 setting更新，但PC SDK不会，只会触发setting更新
      // if (isElectron()) {
      //     this._loadCurrentConversationMessages();
      // }
    })

    wfc.eventEmitter.on(EventType.FriendRequestUpdate, (newFrs) => {
      this._loadFriendRequest()
    })

    wfc.eventEmitter.on(EventType.FriendListUpdate, (updatedFriendIds) => {
      console.log('FriendListUpdate', updatedFriendIds)
      this._loadFriendList()
      this._loadFriendRequest()
      this._loadFavContactList()
      this._loadDefaultConversationList()
      this._patchCurrentConversationMessages()
    })

    wfc.eventEmitter.on(EventType.GroupInfosUpdate, (groupInfos) => {
      // TODO optimize
      console.log('store GroupInfosUpdate', groupInfos)
      this._reloadGroupConversationIfExist(groupInfos)
      this._loadFavGroupList()
      // TODO 其他相关逻辑
    })

    wfc.eventEmitter.on(EventType.GroupMembersUpdate, (groupId, members) => {
      // TODO optimize
      console.log('store GroupMembersUpdate', groupId)
      this._reloadGroupConversationIfExist([new NullGroupInfo(groupId)])
      // this._loadFavGroupList();
      if (
        conversationState.currentConversationInfo &&
        conversationState.currentConversationInfo.conversation.type ===
          ConversationType.Group &&
        conversationState.currentConversationInfo.conversation.target ===
          groupId
      ) {
        this._patchCurrentConversationMessages()
      }
      // TODO 其他相关逻辑
    })

    wfc.eventEmitter.on(EventType.ChannelInfosUpdate, (groupInfos) => {
      this._loadDefaultConversationList()
      this._loadChannelList()
    })

    wfc.eventEmitter.on(
      EventType.ConversationInfoUpdate,
      (conversationInfo) => {
        this._reloadConversation(conversationInfo.conversation)
        // if (conversationState.currentConversationInfo && conversationState.currentConversationInfo.conversation.equal(conversationInfo.conversation)) {
        //     this._loadCurrentConversationMessages();
        // }
        // 标记已读未读
        this.updateTray()
      }
    )

    wfc.eventEmitter.on(EventType.ReceiveMessage, (msg, hasMore) => {
      if (
        miscState.connectionStatus ===
        ConnectionStatus.ConnectionStatusReceiveing
      ) {
        return
      }
      if (
        miscState.isMainWindow &&
        !this.isConversationInCurrentWindow(msg.conversation)
      ) {
        return
      }
      if (
        msg.messageContent instanceof DismissGroupNotification ||
        (msg.messageContent instanceof KickoffGroupMemberNotification &&
          msg.messageContent.kickedMembers.indexOf(wfc.getUserId()) >= 0) ||
        (msg.messageContent instanceof QuitGroupNotification &&
          msg.messageContent.operator === wfc.getUserId())
      ) {
        this.setCurrentConversationInfo(null)
        return
      }
      if (!hasMore) {
        this._reloadConversation(msg.conversation)
      }
      if (
        conversationState.currentConversationInfo &&
        msg.conversation.equal(
          conversationState.currentConversationInfo.conversation
        )
      ) {
        if (msg.messageContent.type === MessageContentType.Typing) {
          let groupId =
            msg.conversation.type === 1 ? msg.conversation.target : ''
          let userInfo = wfc.getUserInfo(msg.from, false, groupId)
          userInfo = Object.assign({}, userInfo)
          userInfo._displayName = wfc.getGroupMemberDisplayNameEx(userInfo)
          conversationState.inputtingUser = userInfo

          if (!conversationState.inputClearHandler) {
            conversationState.inputClearHandler = () => {
              conversationState.inputtingUser = null
            }
          }
          clearTimeout(conversationState.inputClearHandler)
          setTimeout(conversationState.inputClearHandler, 3000)
        } else {
          clearTimeout(conversationState.inputClearHandler)
          conversationState.inputtingUser = null
        }

        if (
          !this._isDisplayMessage(msg) ||
          msg.messageContent.type ===
            MessageContentType.RecallMessage_Notification
        ) {
          return
        }

        if (msg.content.notLoaded) {
          console.log('message not loaded, reset conversation message list')
          conversationState.currentConversationMessageList.length = 0
          conversationState.currentConversationOldestMessageId = 0
          conversationState.currentConversationOldestMessageUid = 0
          return
        }

        // 会把下来加载更多加载的历史消息给清理了
        let lastTimestamp = 0
        let msgListLength =
          conversationState.currentConversationMessageList.length
        if (msgListLength > 0) {
          lastTimestamp =
            conversationState.currentConversationMessageList[msgListLength - 1]
              .timestamp
        }
        this._patchMessage(msg, lastTimestamp)
        let msgIndex =
          conversationState.currentConversationMessageList.findIndex((m) => {
            return (
              m.messageId === msg.messageId ||
              (gt(m.messageUid, 0) && eq(m.messageUid, msg.messageUid)) ||
              (m.messageContent.type ===
                MessageContentType.Streaming_Text_Generating &&
                (msg.messageContent.type ===
                  MessageContentType.Streaming_Text_Generating ||
                  msg.messageContent.type ===
                    MessageContentType.Streaming_Text_Generated) &&
                m.messageContent.streamId === msg.messageContent.streamId)
            )
          })
        if (msgIndex > -1) {
          // FYI: https://v2.vuejs.org/v2/guide/reactivity#Change-Detection-Caveats
          conversationState.currentConversationMessageList.splice(
            msgIndex,
            1,
            msg
          )
          console.log('msg duplicate, update message')
          return
        } else {
          let firstMsg = conversationState.currentConversationMessageList[0]
          if (firstMsg && lt(msg.timestamp, firstMsg.timestamp)) {
            console.log(
              'msg timestamp is less than first msg, maybe update old message content, ignore'
            )
            return
          }
        }

        conversationState.currentConversationMessageList.push(msg)
      }

      if (
        miscState.isMainWindow &&
        this.isConversationInCurrentWindow(msg.conversation)
      ) {
        if (
          msg.conversation.type !== 2 &&
          miscState.isPageHidden &&
          (miscState.enableNotification ||
            msg.status === MessageStatus.AllMentioned ||
            msg.status === MessageStatus.Mentioned)
        ) {
          this.notify(msg)
        }
        this.updateTray()
      }

      if (msg.messageContent instanceof ModifyGroupSettingNotification) {
        wfc.getGroupInfo(msg.messageContent.groupId, true)
      }
    })

    wfc.eventEmitter.on(EventType.RecallMessage, (operator, messageUid) => {
      this._reloadConversationByMessageUidIfExist(messageUid)
      if (conversationState.currentConversationInfo) {
        let msg = wfc.getMessageByUid(messageUid)
        if (msg) {
          if (
            msg.conversation.equal(
              conversationState.currentConversationInfo.conversation
            )
          ) {
            if (conversationState.currentConversationMessageList) {
              let lastTimestamp = 0
              conversationState.currentConversationMessageList =
                conversationState.currentConversationMessageList.map((msg) => {
                  if (eq(msg.messageUid, messageUid)) {
                    let newMsg = wfc.getMessageByUid(messageUid)
                    this._patchMessage(newMsg, lastTimestamp)
                    return newMsg
                  }
                  lastTimestamp = msg.timestamp
                  return msg
                })
            }
          }
        } else {
          conversationState.currentConversationMessageList =
            conversationState.currentConversationMessageList.filter(
              (m) => !eq(m.messageUid, messageUid)
            )
        }
      }
      this.updateTray()
    })

    wfc.eventEmitter.on(EventType.UserOnlineEvent, (userOnlineStatus) => {
      userOnlineStatus.forEach((e) => {
        miscState.userOnlineStateMap.set(e.userId, e)
      })
      // 更新在线状态
      contactState.friendList = this._patchAndSortUserInfos(
        contactState.friendList,
        ''
      )
    })
    // 服务端删除
    wfc.eventEmitter.on(EventType.MessageDeleted, (messageUid) => {
      this._reloadConversationByMessageUidIfExist(messageUid)
      if (conversationState.currentConversationInfo) {
        if (conversationState.currentConversationMessageList) {
          conversationState.currentConversationMessageList =
            conversationState.currentConversationMessageList.filter(
              (msg) => !eq(msg.messageUid, messageUid)
            )
        }
      }
      this.updateTray()
    })
    // 本地删除
    wfc.eventEmitter.on(EventType.DeleteMessage, (messageId) => {
      this._reloadConversationByMessageIdIfExist(messageId)
      if (conversationState.currentConversationInfo) {
        if (conversationState.currentConversationMessageList) {
          conversationState.currentConversationMessageList =
            conversationState.currentConversationMessageList.filter(
              (msg) => msg.messageId !== messageId
            )
        }
      }
    })

    wfc.eventEmitter.on(
      EventType.SecretChatMessageBurned,
      (target, playedMessageId) => {
        // todo 倒计时等
      }
    )

    wfc.eventEmitter.on(EventType.SecretChatMessageBurned, (messageIds) => {
      this._loadDefaultConversationList()
      if (conversationState.currentConversationInfo) {
        if (conversationState.currentConversationMessageList) {
          conversationState.currentConversationMessageList =
            conversationState.currentConversationMessageList.filter(
              (msg) => messageIds.indexOf(msg.messageId) < 0
            )
        }
      }
    })

    wfc.eventEmitter.on(EventType.SecretChatStateChange, (targetId) => {
      this._loadDefaultConversationList()
    })

    wfc.eventEmitter.on(EventType.SendMessage, (message) => {
      // 删除频道，或者从频道会话切到其他会话时，会发送一条离开频道的消息
      if (message.messageContent instanceof LeaveChannelChatMessageContent) {
        return
      }

      this._reloadConversation(message.conversation)
      if (!this._isDisplayMessage(message)) {
        return
      }
      if (
        !conversationState.currentConversationInfo ||
        !message.conversation.equal(
          conversationState.currentConversationInfo.conversation
        )
      ) {
        console.log('not current conv')
        return
      }
      let index = conversationState.currentConversationMessageList.findIndex(
        (m) => m.messageId === message.messageId
      )
      if (index !== -1) {
        return
      }
      let length = conversationState.currentConversationMessageList.length
      let lastTimestamp = 0
      if (length > 0) {
        let lastMessage =
          conversationState.currentConversationMessageList[length - 1]
        lastTimestamp = lastMessage.timestamp
      }
      this._patchMessage(message, lastTimestamp)

      conversationState.currentConversationMessageList.push(message)
      const defaultRenderMessageCount = 50
      if (
        conversationState.currentConversationMessageList.length >
        defaultRenderMessageCount
      ) {
        conversationState.currentConversationMessageList =
          conversationState.currentConversationMessageList.slice(
            conversationState.currentConversationMessageList.length -
              defaultRenderMessageCount
          )
      }
    })

    const messageStatusOrContentUpdateListener = (message) => {
      console.log('message status update', message)
      if (!this._isDisplayMessage(message)) {
        return
      }

      if (
        !conversationState.currentConversationInfo ||
        !message.conversation.equal(
          conversationState.currentConversationInfo.conversation
        )
      ) {
        console.log('not current conv')
        return
      }

      let index = conversationState.currentConversationMessageList.findIndex(
        (m) => m.messageId === message.messageId
      )
      if (index < 0) {
        return
      }
      let msg = conversationState.currentConversationMessageList[index]
      msg = Object.assign(msg, message)
      conversationState.currentConversationMessageList.splice(index, 1, msg)

      if (
        conversationState.currentConversationInfo.lastMessage &&
        conversationState.currentConversationInfo.lastMessage.messageId ===
          message.messageId
      ) {
        Object.assign(
          conversationState.currentConversationInfo.lastMessage,
          message
        )
      }
    }

    wfc.eventEmitter.on(
      EventType.MessageStatusUpdate,
      messageStatusOrContentUpdateListener
    )

    wfc.eventEmitter.on(
      EventType.MessageContentUpdate,
      messageStatusOrContentUpdateListener
    )

    wfc.eventEmitter.on(EventType.MessageRead, (readEntries) => {
      // optimization
      if (conversationState.currentConversationInfo) {
        // wfc.getConversationRead 每次返回同一个对象，只是该对象的值不一样。
        // 由于 VUE 不能检测到初始化时，不存在的属性的变更，故此处重新 new 一个新的对象，并赋值。
        // FYI:https://vuejs.org/v2/guide/reactivity.html
        conversationState.currentConversationRead = new Map(
          wfc.getConversationRead(
            conversationState.currentConversationInfo.conversation
          )
        )
      }
    })

    avenginekitproxy.onVoipCallStatusCallback = this.updateVoipStatus
    if (isElectron()) {
      if (isMainWindow) {
        ipcRenderer.on('deep-link', (event, args) => {
          console.log('deep-link', args)
          if (!wfc.isLogin()) {
            return
          }
          // 下面是示例
          // 可以根据 pathname 和 query parameter 进行相应的逻辑处理，这儿是跳转到对应的会话
          let url = new URL(args)
          let pathname = url.pathname
          let searchParams = url.searchParams
          if ('//conversation' === pathname || '//conversation/' === pathname) {
            let target = searchParams.get('target')
            let line = Number(searchParams.get('line'))
            let type = Number(searchParams.get('type'))
            let conversation = new Conversation(type, target, line)
            this.setCurrentConversation(conversation)
          }
        })

        ipcRenderer.on('floating-conversation-window-closed', (event, args) => {
          let type = args.type
          let target = args.target
          let line = args.line

          let conv = new Conversation(type, target, line)
          this.removeFloatingConversation(conv)
          this._reloadConversation(conv)
        })
      }
      ipcRenderer.on('file-downloaded', (event, args) => {
        let messageUid = args.messageUid
        let localPath = args.filePath
        console.log('file-downloaded', args)

        conversationState.downloadingMessages =
          conversationState.downloadingMessages.filter(
            (v) => !eq(v.messageUid, messageUid)
          )
        let msg = wfc.getMessageByUid(messageUid)
        console.log('xxxxx downloaded file', msg)
        if (msg) {
          msg.messageContent.localPath = localPath
          wfc.updateMessageContent(msg.messageId, msg.messageContent)

          conversationState.currentConversationMessageList.forEach((m) => {
            if (m.messageUid === messageUid) {
              m.messageContent = msg.messageContent
            }
          })
        }
      })

      ipcRenderer.on('file-download-failed', (event, args) => {
        let messageUid = args.messageUid
        conversationState.downloadingMessages =
          conversationState.downloadingMessages.filter(
            (v) => !eq(v.messageUid, messageUid)
          )
        // TODO 其他下载失败处理
      })

      ipcRenderer.on('file-download-progress', (event, args) => {
        let messageUid = args.messageUid
        let receivedBytes = args.receivedBytes
        let totalBytes = args.totalBytes
        let dm = conversationState.downloadingMessages.find((dm) =>
          eq(dm.messageUid, messageUid)
        )
        if (dm) {
          dm.progress = receivedBytes
          dm.total = totalBytes
        }
        // console.log('file download progress', messageId, receivedBytes, totalBytes);
      })

      miscState.subWindowLoadDataOptions = subWindowLoadDataOptions
        ? subWindowLoadDataOptions
        : {}

      if (
        !isMainWindow &&
        wfc.getConnectionStatus() === ConnectionStatus.ConnectionStatusConnected
      ) {
        // 根据 subWindowLoadDataOptions 配置去加载
        this._loadDefaultData()
      }
      window.__wfc = wfc
    }
    miscState.isMainWindow = isMainWindow
    window.__cw = currentWindow
  },

  _loadDefaultData() {
    let loadOptions = miscState.subWindowLoadDataOptions
    let isMainWindow = miscState.isMainWindow
    ;(isMainWindow || loadOptions.loadFavContactList) &&
      this._loadFavGroupList()
    ;(isMainWindow || loadOptions.loadChannelList) && this._loadChannelList()
    ;(isMainWindow || loadOptions.loadFriendList) && this._loadFriendList()
    ;(isMainWindow || loadOptions.loadFavContactList) &&
      this._loadFavContactList()
    ;(isMainWindow || loadOptions.loadFriendRequestList) &&
      this._loadFriendRequest()
    ;(isMainWindow || loadOptions.loadDefaultConversationList) &&
      this._loadDefaultConversationList()
    this._loadSelfUserInfo()
    this._loadUserLocalSettings()
    conversationState.isMessageReceiptEnable =
      wfc.isReceiptEnabled() && wfc.isUserReceiptEnabled()
    conversationState.isGroupMessageReceiptEnable =
      wfc.isGroupReceiptEnabled() && wfc.isUserReceiptEnabled()

    // 休眠恢复之后，重新连接成功时，可能出现会话列表的 lastMessage 在会话界面未显示，需要判断是否需要重新加载当前会话的消息
    if (conversationState.currentConversationInfo) {
      if (
        gt(conversationState.currentConversationInfo.timestamp, 0) &&
        (conversationState.currentConversationMessageList.length === 0 ||
          !eq(
            conversationState.currentConversationInfo.timestamp,
            conversationState.currentConversationMessageList[
              conversationState.currentConversationMessageList.length - 1
            ].timestamp
          ))
      ) {
        this._loadCurrentConversationMessages()
      }
    }
  },

  // conversation actions

  _isDisplayMessage(message) {
    // return [PersistFlag.Persist, PersistFlag.Persist_And_Count].indexOf(MessageConfig.getMessageContentPersitFlag(message.messageContent.type)) > -1;
    return (
      message.messageId !== 0 ||
      message.messageContent.type ===
        MessageContentType.Streaming_Text_Generating
    )
  },

  _loadDefaultConversationList() {
    console.log('store _loadDefaultConversationList')
    let conversationTypes = isElectron() ? [0, 1, 3, 5] : [0, 1, 3]
    this._loadConversationList(conversationTypes, [0])
  },

  _loadConversationList(conversationType = [0, 1, 3], lines = [0]) {
    let conversationList = wfc.getConversationList(conversationType, lines)
    let toLoadUserIdSet = new Set()
    let toLoadGroupIds = []
    conversationList.forEach((info) => {
      if (info.conversation.type === ConversationType.Single) {
        toLoadUserIdSet.add(info.conversation.target)
        if (info.lastMessage && info.lastMessage.from) {
          toLoadUserIdSet.add(info.lastMessage.from)
        }
      } else if (info.conversation.type === ConversationType.Group) {
        toLoadGroupIds.push(info.conversation.target)
        if (info.lastMessage && info.lastMessage.from) {
          toLoadUserIdSet.add(info.lastMessage.from)
        }
      }
    })
    let userInfoMap = new Map()
    let groupInfoMap = new Map()
    toLoadUserIdSet.forEach((uid) => {
      userInfoMap.set(uid, new NullUserInfo(uid))
    })
    toLoadGroupIds.forEach((gid) => {
      groupInfoMap.set(gid, new NullGroupInfo(gid))
    })

    console.log('to load userIds', [...toLoadUserIdSet])
    wfc.getUserInfos([...toLoadUserIdSet]).forEach((u) => {
      userInfoMap.set(u.uid, u)
    })
    console.log('to load groupIds', toLoadGroupIds)
    wfc.getGroupInfos(toLoadGroupIds).forEach((g) => {
      groupInfoMap.set(g.target, g)
    })

    conversationList.forEach((info) => {
      this._patchConversationInfo(info, true, userInfoMap, groupInfoMap)
      // side affect
      if (
        conversationState.currentConversationInfo &&
        conversationState.currentConversationInfo.conversation.equal(
          info.conversation
        )
      ) {
        conversationState.currentConversationInfo = info
        this._patchCurrentConversationOnlineStatus()
      }
    })
    conversationState.conversationInfoList = conversationList
  },

  _reloadConversation(conversation, insertIfNoExist = true) {
    let conversationInfo = wfc.getConversationInfo(conversation)
    if (conversationInfo) {
      conversationInfo = this._patchConversationInfo(conversationInfo, true)
      conversationInfo.conversation._targetOnlineStateDesc =
        this.getUserOnlineState(conversation.target)
    } else {
      return
    }
    let index = conversationState.conversationInfoList.findIndex((info) =>
      info.conversation.equal(conversation)
    )
    if (index >= 0) {
      Object.assign(
        conversationState.conversationInfoList[index],
        conversationInfo
      )
    } else {
      if (
        insertIfNoExist &&
        gt(conversationInfo.timestamp, 0) &&
        conversation.type !== ConversationType.ChatRoom
      ) {
        conversationState.conversationInfoList.push(conversationInfo)
      } else {
        return conversationInfo
      }
    }

    if (
      conversationState.currentConversationInfo &&
      conversationState.currentConversationInfo.conversation.equal(conversation)
    ) {
      let isClearConversationMessageHistory =
        !conversationInfo.lastMessage &&
        !!conversationState.currentConversationInfo.lastMessage
      // 清除聊天记录
      if (isClearConversationMessageHistory) {
        conversationState.currentConversationMessageList = []
      }
      conversationState.currentConversationInfo = conversationInfo
    }

    // sort
    conversationState.conversationInfoList.sort((a, b) => {
      if ((a.top && b.top) || (!a.top && !b.top)) {
        return gt(a.timestamp, b.timestamp) ? -1 : 1
      } else {
        if (a.top) {
          return -1
        } else {
          return 1
        }
      }
    })
    return conversationInfo
  },

  _reloadSingleConversationIfExist(userInfos) {
    let cl = conversationState.conversationInfoList
    if (!cl || cl.length === 0) {
      return
    }
    if (userInfos.length > 10) {
      this._loadDefaultConversationList()
    } else {
      let toReloadConversations = []
      if (cl) {
        let uids = userInfos.map((info) => info.uid)
        cl.forEach((ci) => {
          let conv = ci.conversation
          if (conv.type === ConversationType.Single) {
            if (uids.indexOf(conv.target) >= 0) {
              toReloadConversations.push(conv)
            }
          } else {
            let lastMsg = ci.lastMessage
            if (
              lastMsg &&
              uids.indexOf(lastMsg.from) >= 0 &&
              toReloadConversations.findIndex(
                (c) =>
                  c.type === conv.type &&
                  c.target === conv.target &&
                  c.line === conv.line
              ) === -1
            ) {
              toReloadConversations.push(conv)
            }
          }
        })
      }
      for (let conv of toReloadConversations) {
        this._reloadConversation(conv, false)
      }
    }
  },

  _reloadGroupConversationIfExist(groupInfos) {
    if (groupInfos.length > 10) {
      this._loadDefaultConversationList()
    } else {
      groupInfos.forEach((gi) => {
        let conv = new Conversation(ConversationType.Group, gi.target, 0)
        this._reloadConversation(conv, false)
      })
    }
  },

  _reloadConversationByMessageIdIfExist(messageId) {
    if (messageId === 0) {
      return
    }
    let toLoadConversationInfo = null
    for (let i = 0; i < conversationState.conversationInfoList.length; i++) {
      let info = conversationState.conversationInfoList[i]
      if (info.lastMessage && info.lastMessage.messageId === messageId) {
        toLoadConversationInfo = info
        break
      }
    }

    if (toLoadConversationInfo) {
      this._reloadConversation(toLoadConversationInfo.conversation)
    }
  },
  _reloadConversationByMessageUidIfExist(messageUid) {
    let toLoadConversationInfo = null
    for (let i = 0; i < conversationState.conversationInfoList.length; i++) {
      let info = conversationState.conversationInfoList[i]
      if (info.lastMessage && eq(info.lastMessage.messageUid, messageUid)) {
        toLoadConversationInfo = info
        break
      }
    }

    if (toLoadConversationInfo) {
      this._reloadConversation(toLoadConversationInfo.conversation)
    }
  },

  setCurrentConversation(conversation) {
    if (!conversation) {
      this.setCurrentConversationInfo(null)
      return
    }
    if (
      conversationState.currentConversationInfo &&
      conversation.equal(conversationState.currentConversationInfo.conversation)
    ) {
      return
    }
    let convs = conversationState.conversationInfoList.filter((info) =>
      info.conversation.equal(conversation)
    )
    let info
    if (convs && convs.length > 0) {
      info = convs[0]
    } else {
      wfc.setConversationTimestamp(conversation, new Date().getTime())
      info = this._reloadConversation(conversation)
    }
    this.setCurrentConversationInfo(info)
  },

  setCurrentConversationInfo(conversationInfo) {
    if (!conversationInfo) {
      if (conversationState.currentConversationInfo) {
        let conversation =
          conversationState.currentConversationInfo.conversation

        if (
          wfc.isUserOnlineStateEnabled() &&
          (conversation.type === ConversationType.Single ||
            conversation.type === ConversationType.SecretChat) &&
          !wfc.isMyFriend(conversation.target)
        ) {
          wfc.unwatchOnlineState(conversation.type, [conversation.target])
        }
        if (conversation.type === ConversationType.Channel) {
          let content = new LeaveChannelChatMessageContent()
          wfc.sendConversationMessage(conversation, content)
        }
      }
      conversationState.currentConversationInfo = null
      conversationState.shouldAutoScrollToBottom = false
      conversationState.currentConversationMessageList.length = 0
      conversationState.currentConversationOldestMessageId = 0
      conversationState.currentConversationOldestMessageUid = 0
      conversationState.currentConversationRead = null
      conversationState.enableMessageMultiSelection = false
      conversationState.showChannelMenu = false
      return
    }

    if (
      conversationState.currentConversationInfo &&
      conversationState.currentConversationInfo.conversation.equal(
        conversationInfo.conversation
      )
    ) {
      return
    }
    let conversation = conversationInfo.conversation
    if (
      wfc.isUserOnlineStateEnabled() &&
      (conversation.type === ConversationType.Single ||
        conversation.type === ConversationType.SecretChat) &&
      !wfc.isMyFriend(conversation.target)
    ) {
      wfc.watchOnlineState(
        conversation.type,
        [conversation.target],
        1000,
        (states) => {
          states.forEach((e) => {
            miscState.userOnlineStateMap.set(e.userId, e)
          })
          this._patchCurrentConversationOnlineStatus()
        },
        (err) => {
          console.log('watchOnlineState error', err)
        }
      )
    }
    if (conversation.type === ConversationType.Channel) {
      let content = new EnterChannelChatMessageContent()
      wfc.sendConversationMessage(conversation, content)
    }
    conversationState.currentConversationInfo = conversationInfo
    conversationState.shouldAutoScrollToBottom = true
    conversationState.currentConversationMessageList.length = 0
    conversationState.currentConversationOldestMessageId = 0
    conversationState.currentConversationOldestMessageUid = 0
    // 会话页面会触发调用 loadConversationHistoryMessages，这儿不用提前加载消息
    // this._loadCurrentConversationMessages();
    this._patchCurrentConversationOnlineStatus()

    conversationState.currentConversationRead = wfc.getConversationRead(
      conversationInfo.conversation
    )

    conversationState.enableMessageMultiSelection = false
    conversationState.showChannelMenu = false
    if (conversation.type === ConversationType.Channel) {
      let channelInfo = wfc.getChannelInfo(conversation.target, true)
      if (channelInfo.menus && channelInfo.menus.length > 0) {
        conversationState.showChannelMenu = true
      }
    } else if (conversation.type === ConversationType.Group) {
      wfc.getGroupInfo(conversation.target, true)
    } else if (conversation.type === ConversationType.Single) {
      wfc.getUserInfo(conversation.target, true)
    }
    conversationState.quotedMessage = null
    conversationState.currentVoiceMessage = null

    clearTimeout(conversationState.inputClearHandler)
    conversationState.inputtingUser = null

    pickState.messages.length = 0
  },

  quitGroup(groupId) {
    wfc.quitGroup(
      groupId,
      [0],
      null,
      () => {
        this.setCurrentConversationInfo(null)
      },
      (err) => {
        console.log('quit group error', err)
      }
    )
  },

  dismissGroup(groupId) {
    wfc.dismissGroup(
      groupId,
      [0],
      null,
      () => {
        this.setCurrentConversationInfo(null)
      },
      (err) => {
        console.log('dismiss group error', err)
      }
    )
  },

  subscribeChannel(channelId, subscribe) {
    wfc.listenChannel(
      channelId,
      subscribe,
      () => {
        //this.setCurrentConversationInfo(null)
      },
      (err) => {
        console.log('unsubscribe channel error', err)
      }
    )
  },

  toggleMessageMultiSelection(message) {
    conversationState.enableMessageMultiSelection =
      !conversationState.enableMessageMultiSelection
    pickState.messages.length = 0
    if (conversationState.enableMessageMultiSelection && message) {
      pickState.messages.push(message)
    }
  },

  toggleChannelMenu(toggle) {
    conversationState.showChannelMenu = toggle
  },

  selectOrDeselectMessage(message) {
    let index = pickState.messages.findIndex(
      (m) => m.messageId === message.messageId
    )
    if (index >= 0) {
      pickState.messages.splice(index, 1)
    } else {
      pickState.messages.push(message)
    }
  },

  deleteSelectedMessages(deleteRemoteMessages = false) {
    conversationState.enableMessageMultiSelection = false
    if (pickState.messages.length < 1) {
      return
    }
    pickState.messages.sort((m1, m2) => m1.messageId - m2.messageId)
    pickState.messages.forEach((m) => {
      if (deleteRemoteMessages) {
        wfc.deleteRemoteMessageByUid(m.messageUid)
      } else {
        wfc.deleteMessage(m.messageId)
      }
    })
    pickState.messages.length = 0
  },

  forwardMessage(forwardType, targetConversations, messages, extraMessageText) {
    // web 端，避免撤回消息等操作，影响组合消息
    if (!isElectron()) {
      messages = messages.map((m) => Object.assign({}, m))
    }
    targetConversations.forEach((conversation) => {
      // let msg =new Message(conversation, message.messageContent)
      // wfc.sendMessage(msg)
      // 或者下面这种
      if (
        forwardType === ForwardType.NORMAL ||
        forwardType === ForwardType.ONE_BY_ONE
      ) {
        messages.forEach((message) => {
          if (message.messageContent instanceof ArticlesMessageContent) {
            let linkContents = message.messageContent.toLinkMessageContent()
            linkContents.forEach((lm) => {
              wfc.sendConversationMessage(conversation, lm)
            })
          } else {
            message.messageContent = this._filterForwardMessageContent(message)
            wfc.sendConversationMessage(conversation, message.messageContent)
          }
        })
      } else {
        // 合并转发
        let compositeMessageContent = new CompositeMessageContent()
        let title = ''
        let msgConversation = messages[0].conversation
        if (msgConversation.type === ConversationType.Single) {
          let users = store.getUserInfos(
            [wfc.getUserId(), msgConversation.target],
            ''
          )
          title =
            users[0]._displayName + '和' + users[1]._displayName + '的聊天记录'
        } else {
          title = '群的聊天记录'
        }
        compositeMessageContent.title = title
        let msgs = messages.map((m) => {
          m.messageContent = this._filterForwardMessageContent(m)
          return m
        })
        compositeMessageContent.setMessages(msgs)

        wfc.sendConversationMessage(conversation, compositeMessageContent)
      }

      if (extraMessageText) {
        let textMessage = new TextMessageContent(extraMessageText)
        wfc.sendConversationMessage(conversation, textMessage)
      }
    })
  },

  forwardByCreateConversation(forwardType, users, messages, extraMessageText) {
    this.createConversation(
      users,
      (conversation) => {
        this.forwardMessage(
          forwardType,
          [conversation],
          messages,
          extraMessageText
        )
      },
      (err) => {
        console.error('createConversation error', err)
      }
    )
  },

  setShouldAutoScrollToBottom(scroll) {
    conversationState.shouldAutoScrollToBottom = scroll
  },

  /**
   *
   * @param src {String} 媒体url
   * @param thumbUrl {String} 缩略图url
   * @param thumb {String} base64格式的缩略图，但不包含'data:image/png;base64,'
   * @param autoplay
   */
  previewMedia(src, thumbUrl, thumb, autoplay = true) {
    conversationState.previewMediaItems.length = 0
    conversationState.previewMediaItems.push({
      src: src,
      thumb: thumbUrl ? thumbUrl : 'data:image/png;base64,' + thumb,
      autoplay: autoplay,
    })
    conversationState.previewMediaIndex = 0
    console.log(
      'preview media',
      conversationState.previewMediaItems,
      conversationState.previewMediaIndex
    )
  },
  previewMedias(mediaItems, index) {
    conversationState.previewMediaItems.length = 0
    conversationState.previewMediaItems.push(...mediaItems)
    conversationState.previewMediaIndex = index
    console.log(
      'preview medias',
      conversationState.previewMediaItems,
      conversationState.previewMediaIndex
    )
  },

  playVoice(message) {
    if (conversationState.currentVoiceMessage) {
      conversationState.currentVoiceMessage._isPlaying = false
    }
    conversationState.currentVoiceMessage = message
  },

  /**
   *
   * @param message
   * @param {Boolean} continuous  true，预览周围的媒体消息；false，只预览第一个参数传入的那条媒体消息
   */
  previewMessage(message, continuous) {
    conversationState.previewMediaItems.length = 0
    conversationState.previewMediaIndex = 0
    if (
      continuous &&
      conversationState.currentConversationMessageList.length > 0
    ) {
      let mediaMsgs = conversationState.currentConversationMessageList.filter(
        (m) =>
          [MessageContentType.Image, MessageContentType.Video].indexOf(
            m.messageContent.type
          ) > -1
      )
      let msg
      for (let i = 0; i < mediaMsgs.length; i++) {
        msg = mediaMsgs[i]
        if (msg.messageId === message.messageId) {
          conversationState.previewMediaIndex = i
        }
        let mediaUrl = msg.messageContent.remotePath
        if (!mediaUrl) {
          if (msg.messageContent.file) {
            mediaUrl = URL.createObjectURL(msg.messageContent.file)
          }
        }
        conversationState.previewMediaItems.push({
          src: mediaUrl,
          thumb: 'data:image/png;base64,' + msg.messageContent.thumbnail,
          autoplay: true,
        })
      }
    } else {
      conversationState.previewMediaIndex = 0
      let mediaUrl = message.messageContent.remotePath
      if (!mediaUrl) {
        if (message.messageContent.file) {
          mediaUrl = URL.createObjectURL(message.messageContent.file)
        }
      }
      conversationState.previewMediaItems.push({
        src: mediaUrl,
        thumb: 'data:image/png;base64,' + message.messageContent.thumbnail,
        autoplay: true,
      })
    }
  },

  previewCompositeMessage(compositeMessage, focusMessageUid) {
    conversationState.previewMediaItems.length = 0
    conversationState.previewMediaIndex = 0

    let mediaMsgs = compositeMessage.messageContent.messages.filter(
      (m) =>
        [MessageContentType.Image, MessageContentType.Video].indexOf(
          m.messageContent.type
        ) > -1
    )
    let msg
    for (let i = 0; i < mediaMsgs.length; i++) {
      msg = mediaMsgs[i]
      if (eq(msg.messageUid, focusMessageUid)) {
        conversationState.previewMediaIndex = i
      }
      conversationState.previewMediaItems.push({
        src: msg.messageContent.remotePath,
        thumb: 'data:image/png;base64,' + msg.messageContent.thumbnail,
        autoplay: true,
      })
    }
  },

  /**
   *
   * @param conversation :Conversation 会话
   * @param files : File[] 需要发送的媒体文件
   * @param text : string 描述
   * @return {Promise<void>}
   */
  async sendMixMediaMessage(conversation, files, text) {
    console.log('sendMixMediaMessage', conversation, files, text)
    let isAllImageOrVideoFile = true
    for (let i = 0; i < files.length; i++) {
      if (
        files[i].type.indexOf('image') === -1 &&
        files[i].type.indexOf('video') === -1
      ) {
        isAllImageOrVideoFile = false
        break
      }
    }

    let content
    let entries
    if (isAllImageOrVideoFile) {
      entries = await Promise.all(
        files.map(async (f) => {
          let isImg = f.type.indexOf('image') >= 0
          let {
            thumbnail: it,
            width: iw,
            height: ih,
          } = isImg ? await imageThumbnail(f) : await videoThumbnail(f)
          it = it ? it : Config.DEFAULT_THUMBNAIL_URL
          if (it.length > 6 * 1024) {
            console.warn(
              'generated thumbnail is too large, use default thumbnail',
              it.length
            )
            it = Config.DEFAULT_THUMBNAIL_URL
          }
          return {
            url: '',
            type: isImg ? 'image' : 'video',
            thumbnail: it.split(',')[1],
            width: iw,
            height: ih,
            tmpFile: f,
          }
        })
      )

      content = new MixMultiMediaTextMessageContent(entries, text)
    } else {
      entries = files.map((f) => {
        return {
          url: '',
          name: f.name,
          size: f.size,
          iv: f.type.indexOf('image') >= 0 || f.type.indexOf('video') >= 0,
          tmpFile: f,
        }
      })
      content = new MixFileTextMessageContent(entries, text)
    }
    let msg = wfc.insertMessage(
      conversation,
      content,
      MessageStatus.Sending,
      true
    )

    let entriesWithUrl = await Promise.all(
      entries.map((entry) => {
        let file = entry.tmpFile
        return new Promise((resolve, reject) => {
          wfc.uploadMedia(
            file.name,
            file,
            file.type.indexOf('image') >= 0
              ? MessageContentMediaType.Image
              : MessageContentMediaType.Video,
            (remoteUrl) => {
              entry.url = remoteUrl
              delete entry.tmpFile
              resolve(entry)
            },
            (err) => {
              resolve(entry)
              console.error('upload file failed', f, err)
            },
            (process, total) => {}
          )
        })
      })
    )
    if (isAllImageOrVideoFile) {
      content = new MixMultiMediaTextMessageContent(entriesWithUrl, text)
    } else {
      content = new MixFileTextMessageContent(entries, text)
    }
    wfc.updateMessageContent(msg.messageId, content)
    msg.messageContent = content
    wfc.sendSavedMessage(
      msg,
      0,
      (messageUid, timestamp) => {},
      (err) => {
        console.log('send msg failed', err)
      }
    )
  },

  /**
   *
   * @param conversation
   * @param {File | string} file html File 类型或者url，绝对路径只在electron里面生效
   * @return {Promise<boolean>}
   */
  async sendFile(conversation, file) {
    console.log('send file', file)
    if (file.size && file.size > 100 * 1024 * 1024) {
      if (
        !wfc.isSupportBigFilesUpload() ||
        conversation.type === ConversationType.SecretChat
      ) {
        console.log('file too big, and not support upload big file')
        return true
      }
    }

    let fileOrLocalPath = null
    let remotePath = null
    if (typeof file === 'string') {
      if (!file.startsWith('http')) {
        fileOrLocalPath = file
      } else {
        remotePath = file
      }

      file = {
        path: file,
        name: file.substring(file.lastIndexOf('/') + 1),
      }
    } else {
      fileOrLocalPath = file
    }
    let msg = new Message()
    msg.conversation = conversation

    let mediaType = helper.getMediaType(file.name.split('.').slice(-1).pop())
    // todo other file type
    let messageContentmediaType = {
      pic: MessageContentMediaType.Image,
      video: MessageContentMediaType.Video,
      doc: MessageContentMediaType.File,
    }[mediaType]

    let messageContent
    switch (messageContentmediaType) {
      case MessageContentMediaType.Image:
        let {
          thumbnail: it,
          width: iw,
          height: ih,
        } = await imageThumbnail(file)
        it = it ? it : Config.DEFAULT_THUMBNAIL_URL
        console.log('image file', it.length, file)
        if (it.length > 10 * 1024) {
          console.warn(
            'generated thumbnail is too large, use default thumbnail',
            it.length
          )
          it = Config.DEFAULT_THUMBNAIL_URL
        }
        messageContent = new ImageMessageContent(
          fileOrLocalPath,
          remotePath,
          it.split(',')[1]
        )
        messageContent.imageWidth = iw
        messageContent.imageHeight = ih
        break
      case MessageContentMediaType.Video:
        let vtr = await videoThumbnail(file)
        if (vtr) {
          let { thumbnail: vt, width: vw, height: vh } = vtr
          let duration = await videoDuration(file)
          duration = Math.ceil(duration * 1000)
          if (vt.length > 10 * 1024) {
            console.warn(
              'generated thumbnail is too large, use default thumbnail',
              vt.length
            )
            vt = Config.DEFAULT_THUMBNAIL_URL
          }
          messageContent = new VideoMessageContent(
            fileOrLocalPath,
            remotePath,
            vt.split(',')[1]
          )
          // TODO width and height
          break
        } else {
          // fallback to file message
        }
      case MessageContentMediaType.File:
      // do nothing
      default:
        messageContent = new FileMessageContent(fileOrLocalPath, remotePath)
        break
    }
    msg.messageContent = messageContent
    wfc.sendMessage(
      msg,
      (messageId) => {
        msg.messageId = messageId
        console.log('sf, pr', messageId)
      },
      (progress, total) => {
        // console.log('sf p', msg.messageId, Math.ceil(progress / total * 100))
        let sm = conversationState.sendingMessages.find(
          (e) => e.messageId === msg.messageId
        )
        if (sm) {
          sm.progress = progress
          sm.total = total
        } else {
          conversationState.sendingMessages.push({
            messageId: msg.messageId,
            progress,
            total,
          })
        }
      },
      (messageUid) => {
        console.log('sf s', messageUid)
        conversationState.sendingMessages =
          conversationState.sendingMessages.filter(
            (e) => e.messageId !== msg.messageId
          )
      },
      (error) => {
        console.log('sf e', error)
        conversationState.sendingMessages =
          conversationState.sendingMessages.filter(
            (e) => e.messageId !== msg.messageId
          )
      }
    )
  },

  quoteMessage(message) {
    conversationState.quotedMessage = message
    conversationState.currentConversationInfo._quotedMessage = message
  },

  getConversationInfo(conversation) {
    let info = wfc.getConversationInfo(conversation)
    return this._patchConversationInfo(info, false)
  },

  /**
   * 获取会话消息
   * @param {Conversation} conversation 会话
   * @param {number} fromIndex 其实消息的 messageId
   * @param {boolean} before 获取其实消息之前，还是之后的消息
   * @param {string} withUser 过滤该用户发送或接收的消息
   * @param {function (Message[]) } callback 消息列表会回调
   */
  getMessages(
    conversation,
    fromIndex = 0,
    before = true,
    withUser = '',
    callback
  ) {
    wfc.getMessagesV2(
      conversation,
      fromIndex,
      before,
      20,
      withUser,
      (msgs) => {
        msgs = msgs.map((m) => this._patchMessage(m, 0))
        //callback && callback(msgs);
        setTimeout(() => callback && callback(msgs), 200)
      },
      (err) => {
        console.error('getMessageV2 error', err)
        callback && callback([])
      }
    )
  },

  getMessageInTypes(
    conversation,
    contentTypes,
    timestamp,
    before = true,
    withUser = '',
    callback
  ) {
    wfc.getMessagesByTimestampV2(
      conversation,
      contentTypes,
      timestamp,
      before,
      20,
      withUser,
      (msgs) => {
        msgs = msgs.map((m) => this._patchMessage(m, 0))
        callback && callback(msgs)
      },
      (err) => {
        callback && callback([])
      }
    )
  },

  _loadCurrentConversationMessages() {
    console.log('_loadCurrentConversationMessages')
    if (!conversationState.currentConversationInfo) {
      return
    }
    // TODO 可以在这儿加载所有未读消息，以实现滚动到一条未读消息的地方
    let conversation = conversationState.currentConversationInfo.conversation
    wfc.getMessagesV2(
      conversation,
      0,
      true,
      20,
      '',
      (msgs) => {
        conversationState.currentConversationMessageList = msgs
        this._patchCurrentConversationMessages()
        if (msgs.length) {
          conversationState.currentConversationOldestMessageId =
            msgs[0].messageId
        }
        for (let i = 0; i < msgs.length; i++) {
          if (gt(msgs[i].messageUid, 0)) {
            conversationState.currentConversationOldestMessageUid =
              msgs[0].messageUid
            break
          }
        }
      },
      (err) => {
        console.error('_loadCurrentConversationMessages error', err)
      }
    )
  },

  _patchCurrentConversationMessages() {
    let lastTimestamp = 0
    let msgs = conversationState.currentConversationMessageList
    msgs.forEach((m) => {
      this._patchMessage(m, lastTimestamp)
      lastTimestamp = m.timestamp
    })
  },

  _onloadConversationMessages(conversation, messages) {
    if (!messages || messages.length === 0) {
      return false
    }
    let loadNewMsg = false
    let lastTimestamp = 0
    let newMsgs = []
    messages.forEach((m) => {
      let index = conversationState.currentConversationMessageList.findIndex(
        (cm) => cm.messageId === m.messageId
      )
      if (index === -1) {
        this._patchMessage(m, lastTimestamp)
        lastTimestamp = m.timestamp
        newMsgs.push(m)
        loadNewMsg = true
      }
    })
    conversationState.currentConversationMessageList = newMsgs.concat(
      conversationState.currentConversationMessageList
    )
    return loadNewMsg
  },

  loadConversationHistoryMessages(
    loadedCB,
    completeCB,
    enableLoadRemoteHistoryMessage = true
  ) {
    if (!conversationState.currentConversationInfo) {
      return
    }
    let conversation = conversationState.currentConversationInfo.conversation
    console.log(
      'loadConversationHistoryMessage',
      conversation,
      conversationState.currentConversationOldestMessageId,
      stringValue(conversationState.currentConversationOldestMessageUid)
    )
    let loadRemoteHistoryMessageFunc = () => {
      console.log(
        'loadRemoteConversationMessages',
        conversation,
        conversationState.currentConversationOldestMessageUid
      )
      wfc.loadRemoteConversationMessages(
        conversation,
        [],
        conversationState.currentConversationOldestMessageUid,
        20,
        (msgs) => {
          console.log('loadRemoteConversationMessages response', msgs.length)
          if (msgs.length === 0) {
            completeCB()
          } else {
            // 可能拉回来的时候，本地已经切换会话了
            if (
              conversation.equal(
                conversationState.currentConversationInfo.conversation
              )
            ) {
              let filteredMsgs = msgs.filter((m) => {
                return (
                  m.messageId !== 0 &&
                  conversationState.currentConversationMessageList.findIndex(
                    (cm) => eq(cm.messageUid, m.messageUid)
                  ) === -1
                )
              })
              if (filteredMsgs.length === 0) {
                completeCB()
                return
              }

              conversationState.currentConversationOldestMessageUid =
                filteredMsgs[0].messageUid
              this._onloadConversationMessages(conversation, filteredMsgs)
              loadedCB()
            }
            if (!conversationState.currentConversationInfo.lastMessage) {
              this._reloadConversation(conversation)
            }
          }
        },
        (error) => {
          completeCB()
        }
      )
    }

    wfc.getMessagesV2(
      conversation,
      conversationState.currentConversationOldestMessageId,
      true,
      20,
      '',
      (lmsgs) => {
        if (lmsgs.length > 0) {
          if (
            !conversation.equal(
              conversationState.currentConversationInfo.conversation
            )
          ) {
            return
          }
          conversationState.currentConversationOldestMessageId =
            lmsgs[0].messageId
          if (gt(lmsgs[0].messageUid, 0)) {
            conversationState.currentConversationOldestMessageUid =
              lmsgs[0].messageUid
          }
          this._onloadConversationMessages(conversation, lmsgs)
          if (lmsgs.length === 0) {
            if (enableLoadRemoteHistoryMessage) {
              loadRemoteHistoryMessageFunc()
            } else {
              completeCB()
            }
          } else {
            // loadedCB();
            setTimeout(() => loadedCB(), 200)
          }
        } else {
          if (enableLoadRemoteHistoryMessage) {
            loadRemoteHistoryMessageFunc()
          } else {
            completeCB()
          }
        }
      },
      (err) => {
        completeCB()
      }
    )
  },

  setConversationTop(conversation, top) {
    wfc.setConversationTop(
      conversation,
      top,
      () => {
        this._reloadConversation(conversation)
      },
      (err) => {
        console.log('setConversationTop error', err)
      }
    )
  },

  setConversationSilent(conversation, silent) {
    wfc.setConversationSlient(
      conversation,
      silent,
      () => {
        this._reloadConversation(conversation)
      },
      (err) => {
        console.log('setConversationSilent error', err)
      }
    )
  },

  removeConversation(conversation) {
    wfc.removeConversation(conversation, false)
    if (
      conversationState.currentConversationInfo &&
      conversationState.currentConversationInfo.conversation.equal(conversation)
    ) {
      this.setCurrentConversationInfo(null)
    }
    conversationState.conversationInfoList =
      conversationState.conversationInfoList.filter(
        (info) => !info.conversation.equal(conversation)
      )
    this.updateTray()
  },

  getMessageById(messageId) {
    let msg = wfc.getMessageById(messageId)
    if (msg) {
      this._patchMessage(msg, 0)
    }
    return msg
  },

  getMessageByUid(messageUid) {
    let msg = wfc.getMessageByUid(messageUid)
    if (msg) {
      this._patchMessage(msg, 0)
    }
    return msg
  },

  _patchMessage(m, lastTimestamp = 0, userInfoMap) {
    // TODO
    // _from
    // _showTime
    if (m.conversation.type === ConversationType.Single) {
      m._from = userInfoMap
        ? userInfoMap.get(m.from)
        : wfc.getUserInfo(m.from, false, '')
    }
    if (!m._from || !m._from.updateDt) {
      let u = wfc.getUserInfo(
        m.from,
        false,
        m.conversation.type === ConversationType.Group
          ? m.conversation.target
          : ''
      )
      // clone for modify
      // TODO sdk 返回的时候，直接返回 clone copy，而不是直接返回底层的数据，防止上层修改，影响到底层数据模型
      m._from = Object.assign({}, u)
    }
    if (m.conversation.type === ConversationType.Group) {
      m._from._displayName = wfc.getGroupMemberDisplayNameEx(m._from)
    } else {
      m._from._displayName = wfc.getUserDisplayNameEx(m._from)
    }
    if (m.conversation.type === ConversationType.SecretChat) {
      if (
        m.messageContent instanceof MediaMessageContent &&
        m.messageContent.remotePath &&
        m.messageContent.remotePath.startsWith('http')
      ) {
        m.messageContent.remotePath = `http://localhost:${Config.SECRET_CHAT_MEDIA_DECODE_SERVER_PORT}?target=${m.conversation.target}&url=${m.messageContent.remotePath}`
      }
    }

    if (
      lastTimestamp === 0 ||
      (numberValue(lastTimestamp) > 0 &&
        numberValue(m.timestamp) - numberValue(lastTimestamp) > 5 * 60 * 1000)
    ) {
      m._showTime = true
    }
    m._timeStr = helper.timeFormat(m.timestamp)

    if (m.messageContent instanceof CompositeMessageContent) {
      this._patchCompositeMessageContent(m.messageContent)
    }

    // TODO 如果Im server支持备选网络，需要根据当前的网络情况，判断当前是处于主网络，还是备选网络，并动态修改媒体类消息的remotePath，不然可能会出现不能正常加载的情况
    // 如何判断是主网络，还是备选网络，这儿提供一种思路：分别通过主网络和备选网络测试访问im server的/api/version接口
    // 判断是主网络，还是备选网络，一般启动的时候，检测到网络网络变化的时候，在判断一次。
    // if(m.messageContent instanceof MediaMessageContent){
    // TODO 动态修改remotePath
    // }

    return m
  },

  _patchCompositeMessageContent(compositeMessageContent) {
    let messages = compositeMessageContent.messages
    messages.forEach((m) => {
      this._patchMessage(m, 0)
    })
  },

  _patchConversationInfo(
    info,
    patchLastMessage = true,
    userInfoMap,
    groupInfoMap
  ) {
    if (info.conversation.type === ConversationType.Single) {
      info.conversation._target = userInfoMap
        ? userInfoMap.get(info.conversation.target)
        : wfc.getUserInfo(info.conversation.target, false)
      if (info.conversation._target) {
        info.conversation._target._displayName = wfc.getUserDisplayNameEx(
          info.conversation._target
        )
      }
    } else if (info.conversation.type === ConversationType.Group) {
      info.conversation._target = groupInfoMap
        ? groupInfoMap.get(info.conversation.target)
        : wfc.getGroupInfo(info.conversation.target, false)
      if (info.conversation._target) {
        info.conversation._target._isFav = wfc.isFavGroup(
          info.conversation.target
        )
        info.conversation._target._displayName = info.conversation._target
          .remark
          ? info.conversation._target.remark
          : info.conversation._target.name
      }
    } else if (info.conversation.type === ConversationType.Channel) {
      info.conversation._target = wfc.getChannelInfo(
        info.conversation.target,
        false
      )
      info.conversation._target._displayName = info.conversation._target.name
    } else if (info.conversation.type === ConversationType.SecretChat) {
      let secretChatInfo = wfc.getSecretChatInfo(info.conversation.target)
      if (secretChatInfo) {
        let userId = secretChatInfo.userId
        let userInfo = wfc.getUserInfo(userId, false)
        info.conversation._target = userInfo
        info.conversation._target._displayName =
          wfc.getUserDisplayNameEx(userInfo)
      } else {
        info.conversation._target = {}
      }
    } else if (info.conversation.type === ConversationType.ChatRoom) {
      wfc.getChatroomInfo(
        info.conversation.target,
        0,
        (chatRoomInfo) => {
          info.conversation._target = chatRoomInfo
        },
        (err) => {
          console.log('get chatRoomInfo error', err)
          info.conversation._target = {}
        }
      )
    }
    if (gt(info.timestamp, 0)) {
      info._timeStr = helper.dateFormat(info.timestamp)
    } else {
      info._timeStr = ''
    }

    if (
      patchLastMessage &&
      info.lastMessage &&
      info.lastMessage.conversation !== undefined &&
      (!info.lastMessage._from || !info.lastMessage._from.updateDt)
    ) {
      this._patchMessage(info.lastMessage, 0, userInfoMap)
    }

    if (info.unreadCount) {
      info._unread =
        info.unreadCount.unread +
        info.unreadCount.unreadMention +
        info.unreadCount.unreadMentionAll
    }
    if (info.conversation.equal(avenginekitproxy.conversation)) {
      info._isVoipOngoing = true
    } else {
      info._isVoipOngoing = false
    }

    return info
  },

  addDownloadingMessage(messageUid) {
    conversationState.downloadingMessages.push({
      messageUid: messageUid,
      progress: 0,
      total: Number.MAX_SAFE_INTEGER,
    })
    console.log('add downloading')
  },

  isDownloadingMessage(messageUid) {
    // web端尚未测试，先屏蔽
    if (!isElectron()) {
      return false
    }
    return (
      conversationState.downloadingMessages.findIndex((dm) =>
        eq(dm.messageUid, messageUid)
      ) >= 0
    )
  },

  isSendingMessage(messageId) {
    return conversationState.sendingMessages.has(messageId)
  },

  getDownloadingMessageStatus(messageUid) {
    return conversationState.downloadingMessages.find((dm) =>
      eq(dm.messageUid, messageUid)
    )
  },

  getSendingStatus(messageId) {
    return conversationState.sendingMessages.find(
      (e) => e.messageId === messageId
    )
  },

  addFloatingConversation(conversation) {
    conversationState.floatingConversations.push(conversation)
  },

  removeFloatingConversation(conversation) {
    conversationState.floatingConversations =
      conversationState.floatingConversations.filter(
        (c) => !c.equal(conversation)
      )
  },

  isConversationInCurrentWindow(conversation) {
    if (miscState.isMainWindow) {
      let index = conversationState.floatingConversations.findIndex((fc) =>
        fc.equal(conversation)
      )
      return index === -1
    } else {
      return (
        conversationState.currentConversationInfo &&
        conversationState.currentConversationInfo.conversation.equal(
          conversation
        )
      )
    }
  },

  // contact actions

  _loadSelfUserInfo() {
    contactState.selfUserInfo = wfc.getUserInfo(wfc.getUserId(), false)
  },

  _loadFriendList() {
    let friends = wfc.getMyFriendList(false)
    if (Config.FILE_HELPER_ID) {
      let fileHelperIndex = friends.indexOf(Config.FILE_HELPER_ID)
      if (fileHelperIndex < 0 && Config.FILE_HELPER_ID) {
        friends.push(Config.FILE_HELPER_ID)
      }
    }
    if (friends && friends.length > 0) {
      let friendList = wfc.getUserInfos(friends, '')
      contactState.friendList = this._patchAndSortUserInfos(friendList, '')
    }
  },

  getUserOnlineState(userId) {
    let userOnlineState = miscState.userOnlineStateMap.get(userId)
    if (userOnlineState) {
      return userOnlineState.desc()
    }
    return ''
  },

  _patchCurrentConversationOnlineStatus() {
    let convInfo = conversationState.currentConversationInfo
    if (convInfo && convInfo.conversation.type === ConversationType.Single) {
      // 在 将 object 和 ui 绑定之前， 向 object 中新增的属性是 reactive 的，但绑定之后，才新增的属性，不是 reactive 的，
      // 故需要通过下面这种方法，让其成为 reactive 的属性
      // conversationState.currentConversationInfo.conversation._targetOnlineStateDesc = userOnlineStatus.desc();
      // Vue.set(conversationState.currentConversationInfo.conversation, '_targetOnlineStateDesc', this.getUserOnlineState(convInfo.conversation.target))
      conversationState.currentConversationInfo.conversation._targetOnlineStateDesc =
        this.getUserOnlineState(convInfo.conversation.target)
    } else {
      //TODO
    }
  },
  _loadFriendRequest() {
    let requests = wfc.getIncommingFriendRequest()

    requests.sort((a, b) => numberValue(b.timestamp) - numberValue(a.timestamp))
    requests = requests.length >= 20 ? requests.slice(0, 20) : requests
    let uids = []
    requests.forEach((fr) => {
      uids.push(fr.target)
    })
    let userInfos = wfc.getUserInfos(uids, '')
    requests.forEach((fr) => {
      let userInfo = userInfos.find((u) => u.uid === fr.target)
      fr._target = userInfo
    })

    contactState.friendRequestList = requests
    contactState.unreadFriendRequestCount = wfc.getUnreadFriendRequestCount()
  },

  _patchAndSortUserInfos(userInfos, groupId = '', compareFn) {
    userInfos = userInfos.map((u) => {
      if (groupId) {
        u._displayName = wfc.getGroupMemberDisplayNameEx(u)
        u._displayNameIgnoreFriendAlias = wfc.getGroupMemberDisplayNameEx(
          u,
          true
        )
      } else {
        u._displayName = wfc.getUserDisplayNameEx(u)
      }
      u._pinyin = convert(u._displayName, { style: 0 })
        .join('')
        .trim()
        .toLowerCase()
      let firstLetter = u._pinyin[0]
      if (firstLetter >= 'a' && firstLetter <= 'z') {
        u.__sortPinyin = 'a' + u._pinyin
      } else {
        u.__sortPinyin = 'z' + u._pinyin
      }
      u._firstLetters = convert(u._displayName, { style: 4 })
        .join('')
        .trim()
        .toLowerCase()
      return u
    })
    if (compareFn) {
      userInfos = userInfos.sort(compareFn)
    } else {
      userInfos = userInfos.sort((a, b) =>
        a.__sortPinyin.localeCompare(b.__sortPinyin)
      )
    }

    userInfos.forEach((u) => {
      let uFirstLetter = u.__sortPinyin[1]
      if (uFirstLetter >= 'a' && uFirstLetter <= 'z') {
        u._category = uFirstLetter
      } else {
        u._category = '#'
      }
      u._userOnlineStatusDesc = this.getUserOnlineState(u.uid)
    })
    return userInfos
  },

  _loadFavGroupList() {
    contactState.favGroupList = wfc.getFavGroupList()
  },

  _loadChannelList() {
    wfc.getRemoteListenedChannels(
      (channelIds) => {
        if (channelIds) {
          contactState.channelList = channelIds.map((channelId) =>
            wfc.getChannelInfo(channelId, false)
          )
          contactState.channelList = contactState.channelList.filter((ch) => {
            return !(ch instanceof NullChannelInfo)
          })
        }
      },
      (err) => {
        console.error('getRemoteListenedChannels error', err)
      }
    )
  },

  _loadFavContactList() {
    let favUserIds = wfc.getFavUsers()
    if (favUserIds.length > 0) {
      contactState.favContactList = this.getUserInfos(favUserIds, '')
      contactState.favContactList.forEach((u) => {
        u._category = '☆ 星标朋友'
      })
    } else {
      contactState.favContactList = []
    }
  },

  reloadFavGroupList() {
    this._loadFavGroupList()
  },

  setCurrentFriendRequest(friendRequest) {
    contactState.currentFriendRequest = friendRequest
    contactState.currentFriend = null
    contactState.currentOrganization = null
    contactState.currentChatroom = null
    contactState.currentGroup = null
    contactState.currentChannel = null
    contactState.currentExternalDomain = null
  },

  setCurrentFriend(friend) {
    contactState.currentFriendRequest = null
    contactState.currentFriend = friend
    contactState.currentOrganization = null
    contactState.currentChatroom = null
    contactState.currentGroup = null
    contactState.currentChannel = null
    contactState.currentExternalDomain = null
  },

  setCurrentGroup(group) {
    contactState.currentFriendRequest = null
    contactState.currentFriend = null
    contactState.currentOrganization = null
    contactState.currentChatroom = null
    contactState.currentGroup = group
    contactState.currentChannel = null
    contactState.currentExternalDomain = null
  },

  setCurrentChannel(channel) {
    contactState.currentFriendRequest = null
    contactState.currentFriend = null
    contactState.currentOrganization = null
    contactState.currentChatroom = null
    contactState.currentGroup = null
    contactState.currentChannel = channel
    contactState.currentExternalDomain = null
  },

  setCurrentOrganization(organization) {
    contactState.currentFriendRequest = null
    contactState.currentFriend = null
    contactState.currentGroup = null
    contactState.currentChannel = null
    contactState.currentChatroom = null
    contactState.currentOrganization = organization
    contactState.currentExternalDomain = null
  },
  setCurrentExternalDomain(domainInfo) {
    contactState.currentFriendRequest = null
    contactState.currentFriend = null
    contactState.currentGroup = null
    contactState.currentChannel = null
    contactState.currentChatroom = null
    contactState.currentOrganization = null
    contactState.currentExternalDomain = domainInfo
  },
  setCurrentChatroom(chatroom) {
    contactState.currentFriendRequest = null
    contactState.currentFriend = null
    contactState.currentGroup = null
    contactState.currentChannel = null
    contactState.currentOrganization = null
    contactState.currentChatroom = chatroom
    contactState.currentExternalDomain = null
  },
  toggleGroupList() {
    contactState.expandGroup = !contactState.expandGroup
  },

  toggleChannelList() {
    contactState.expandChanel = !contactState.expandChanel
    // 从服务端拉取，且比较耗性能，故展开时，才从刷新
    if (contactState.expandChanel) {
      this._loadChannelList()
    }
  },

  toggleFriendRequestList() {
    contactState.expandFriendRequestList = !contactState.expandFriendRequestList
  },

  toggleFriendList() {
    contactState.expandFriendList = !contactState.expandFriendList
  },

  toggleOrganizationList() {
    contactState.expandOrganization = !contactState.expandOrganization
  },

  toggleExternalDomainList() {
    contactState.expandExternalDomain = !contactState.expandExternalDomain
  },
  toggleChatroom() {
    contactState.expandChatroom = !contactState.expandChatroom
  },

  // search actions
  hideSearchView() {
    searchState.query = ''
  },

  setSearchQuery(query) {
    searchState.query = query
    if (query) {
      console.log('search', query)
      if (searchState.searchDomainInfo) {
        this.searchUser(query, searchState.searchDomainInfo.domainId)
      } else {
        searchState.contactSearchResult = this.filterContact(query)
        searchState.groupSearchResult = this.filterGroupConversation(query)
        searchState.conversationSearchResult = this.filterConversation(query)
        // searchState.messageSearchResult = this.searchMessage(query);
        this.searchUser(query)
        this.searchChannel(query)
      }
    } else {
      searchState._reset()
    }
  },

  setSearchDomainInfo(domainInfo) {
    searchState.searchDomainInfo = domainInfo
  },

  searchUser(query, domainId = '') {
    console.log('search user', query)
    wfc.searchUserEx(
      domainId,
      query,
      SearchType.General,
      0,
      (keyword, userInfos) => {
        console.log('search user result', query, userInfos)
        if (searchState.query === keyword) {
          searchState.userSearchResult = userInfos.filter(
            (u) => !wfc.isMyFriend(u.uid)
          )
        }
      },
      (err) => {
        console.log('search user error', query, err)
        if (searchState.query === query) {
          searchState.userSearchResult = []
        }
      }
    )
  },

  searchChannel(query) {
    console.log('search channel')
    wfc.searchChannel(
      query,
      true,
      (keyword, channelInfos) => {
        console.log('search channel result', channelInfos)
        if (searchState.query === keyword) {
          console.log('search channel result', channelInfos)
          searchState.channelSearchResult = channelInfos
        }
      },
      (err) => {
        console.log('search channel error', query, err)
        if (searchState.query === query) {
          searchState.channelSearchResult = []
        }
      }
    )
  },

  // TODO 到底是什么匹配了
  filterContact(query) {
    let result = contactState.friendList.filter((u) => {
      return (
        u._displayName.indexOf(query) > -1 ||
        u._firstLetters.indexOf(query.toLowerCase()) > -1 ||
        u._pinyin.indexOf(query.toLowerCase()) > -1
      )
    })

    console.log('friend searchResult', result)
    return result
  },

  searchFiles(keyword, beforeMessageUid, successCB, failCB) {
    if (!keyword) {
      return
    }
    wfc.searchFiles(
      keyword,
      null,
      '',
      beforeMessageUid,
      0,
      20,
      (files) => {
        this._patchFileRecords(files)
        successCB && successCB(files)
      },
      (errorCode) => {
        console.log('search file error', errorCode)
        failCB && failCB(errorCode)
      }
    )
  },

  filterUsers(users, filter) {
    if (!users || !filter || !filter.trim()) {
      return users
    }
    let queryPinyin = convert(filter, { style: 0 })
      .join('')
      .trim()
      .toLowerCase()
    let result = users.filter((u) => {
      return (
        u._displayName.indexOf(filter) > -1 ||
        u._displayName.indexOf(queryPinyin) > -1 ||
        u._pinyin.indexOf(filter) > -1 ||
        u._pinyin.indexOf(queryPinyin) > -1 ||
        u._firstLetters.indexOf(filter) > -1 ||
        u._firstLetters.indexOf(queryPinyin) > -1
      )
    })
    return result
  },

  // TODO 匹配类型，是群名称匹配上了，还是群成员的名称匹配上了？
  // 目前只搜索群名称
  filterFavGroup(query) {
    console.log('to search group', contactState.favGroupList)
    let queryPinyin = convert(query, { style: 0 }).join('').trim().toLowerCase()
    let result = contactState.favGroupList.filter((g) => {
      let groupNamePinyin = convert(g.name, { style: 0 })
        .join('')
        .trim()
        .toLowerCase()
      return (
        g.name.indexOf(query) > -1 ||
        g.name.indexOf(queryPinyin) > -1 ||
        groupNamePinyin.indexOf(query) > -1 ||
        groupNamePinyin.indexOf(queryPinyin) > -1
      )
    })

    console.log('group searchResult', result)
    return result
  },

  filterConversation(query) {
    return conversationState.conversationInfoList.filter((info) => {
      let displayNamePinyin = convert(info.conversation._target._displayName, {
        style: 0,
      })
        .join('')
        .trim()
        .toLowerCase()
      let firstLetters = convert(info.conversation._target._displayName, {
        style: 4,
      })
        .join('')
        .trim()
        .toLowerCase()
      return (
        info.conversation._target._displayName.indexOf(query) > -1 ||
        displayNamePinyin.indexOf(query.toLowerCase()) > -1 ||
        firstLetters.indexOf(query) > -1
      )
    })
  },

  filterGroupConversation(query) {
    // query = query.toLowerCase();
    // let groups = conversationState.conversationInfoList.filter(info => info.conversation.type === ConversationType.Group).map(info => info.conversation._target);
    // return groups.filter(groupInfo => {
    //     let namePinyin = convert(groupInfo.name, {style: 0}).join('').trim().toLowerCase();
    //     let firstLetters = convert(groupInfo.name, {style: 4}).join('').trim().toLowerCase();
    //     return groupInfo.name.indexOf(query) > -1 || namePinyin.indexOf(query) > -1 || firstLetters.indexOf(query) > -1
    // })
    let gsr = wfc.searchGroups(query)
    return gsr.map((r) => r.groupInfo)
  },

  searchMessage(conversation, query) {
    let msgs = wfc.searchMessage(conversation, query)
    msgs = msgs.reverse()
    return msgs.map((m) => this._patchMessage(m, 0))
  },

  searchMessageInTypes(conversation, contentTypes, query, offset) {
    let msgs = wfc.searchMessageByTypes(
      conversation,
      query,
      contentTypes,
      true,
      20,
      offset
    )
    return msgs.map((m) => this._patchMessage(m, 0))
  },

  searchConversation(query, types = [0, 1, 2], lines = [0, 1, 2]) {
    let results = wfc.searchConversation(query, types, lines)
    return results.map((r) => {
      let info = wfc.getConversationInfo(r.conversation)
      r._conversationInfo = this._patchConversationInfo(info, false)
      return r
    })
  },

  // pick actions
  pickOrUnpickUser(user) {
    let index = pickState.users.findIndex((u) => u.uid === user.uid)
    if (index >= 0) {
      pickState.users = pickState.users.filter((u) => user.uid !== u.uid)
    } else {
      pickState.users.push(user)
    }
  },

  isUserPicked(user) {
    let index = pickState.users.findIndex((u) => u.uid === user.uid)
    return index >= 0
  },

  // pick actions
  pickOrUnpickOrganization(org) {
    let index = pickState.organizations.findIndex((o) => o.id === org.id)
    if (index >= 0) {
      pickState.organizations = pickState.organizations.filter(
        (o) => o.id !== org.id
      )
    } else {
      pickState.organizations.push(org)
    }
  },

  isOrganizationPicked(org) {
    let index = pickState.organizations.findIndex((o) => o.id === org.id)
    return index >= 0
  },

  pickOrUnpickConversation(conversation) {
    let index = pickState.conversations.findIndex(
      (c) =>
        conversation.target === c.target &&
        conversation.line === c.line &&
        conversation.type === c.type
    )
    if (index >= 0) {
      pickState.conversations = pickState.conversations.filter(
        (c) =>
          !(
            conversation.target === c.target &&
            conversation.line === c.line &&
            conversation.type === c.type
          )
      )
    } else {
      pickState.conversations.push(conversation)
    }
  },

  // misc actions
  createConversation(users, successCB, failCB) {
    if (users.length === 1) {
      let conversation = new Conversation(
        ConversationType.Single,
        users[0].uid,
        0
      )
      this.setCurrentConversation(conversation)
      successCB && successCB(conversation)
      return
    }

    let groupName = contactState.selfUserInfo.displayName
    let groupMemberIds = []
    for (let i = 0; i < users.length; i++) {
      groupMemberIds.push(users[i].uid)
      if (i <= 3) {
        groupName += '、' + users[i].displayName
      }
    }
    groupName = groupName.substr(0, groupName.length - 1)
    wfc.createGroup(
      null,
      GroupType.Restricted,
      groupName,
      null,
      null,
      groupMemberIds,
      null,
      [0],
      null,
      (groupId) => {
        let conversation = new Conversation(ConversationType.Group, groupId, 0)
        this.setCurrentConversation(conversation)
        successCB && successCB(conversation)
      },
      (error) => {
        console.log('create group error', error)
        failCB && failCB(error)
      }
    )
  },

  _loadUserLocalSettings() {
    let userId = wfc.getUserId()
    // 默认允许通知
    let setting = getItem(userId + '-' + 'notification')
    miscState.enableNotification = setting === null || setting === '1'
    setting = getItem(userId + '-' + 'notificationDetail')
    miscState.enableNotificationMessageDetail =
      setting === null || setting === '1'
    miscState.enableCloseWindowToExit =
      getItem(userId + '-' + 'closeWindowToExit') === '1'
    miscState.enableAutoLogin = getItem(userId + '-' + 'autoLogin') === '1'
    setting = getItem('minimizable')
    miscState.enableMinimize = setting === null || setting === '1'
  },

  setEnableNotification(enable) {
    miscState.enableNotification = enable
    setItem(
      contactState.selfUserInfo.uid + '-' + 'notification',
      enable ? '1' : '0'
    )
  },

  setEnableMinimize(enable) {
    miscState.enableMinimize = enable
    setItem('minimizable', enable ? '1' : '0')
    currentWindow.minimizable = enable
  },

  setEnableNotificationDetail(enable) {
    miscState.enableNotificationMessageDetail = enable
    setItem(
      contactState.selfUserInfo.uid + '-' + 'notificationDetail',
      enable ? '1' : '0'
    )
  },

  setEnableCloseWindowToExit(enable) {
    miscState.enableCloseWindowToExit = enable
    setItem(
      contactState.selfUserInfo.uid + '-' + 'closeWindowToExit',
      enable ? '1' : '0'
    )
    ipcRenderer.send(IPCEventType.ENABLE_CLOSE_WINDOW_TO_EXIT, enable)
  },

  setEnableAutoLogin(enable) {
    miscState.enableAutoLogin = enable
    setItem(
      contactState.selfUserInfo.uid + '-' + 'autoLogin',
      enable ? '1' : '0'
    )
  },

  // clone一下，别影响到好友列表
  getUserInfos(userIds, groupId) {
    let userInfos = wfc.getUserInfos(userIds, groupId)
    let userInfosCloneCopy = userInfos.map((u) => Object.assign({}, u))
    return this._patchAndSortUserInfos(userInfosCloneCopy, groupId)
  },

  // clone一下，别影响到好友列表
  getGroupMemberUserInfos(groupId, includeSelf = true, sortByPinyin = false) {
    let memberIds = wfc.getGroupMemberIds(groupId)
    let userInfos = wfc.getUserInfos(memberIds, groupId)
    if (!includeSelf) {
      userInfos = userInfos.filter((u) => u.uid !== wfc.getUserId())
    }
    let userInfosCloneCopy = userInfos.map((u) => Object.assign({}, u))
    if (sortByPinyin) {
      return this._patchAndSortUserInfos(userInfosCloneCopy, groupId)
    } else {
      let compareFn = (u1, u2) => {
        let index1 = memberIds.findIndex((id) => id === u1.uid)
        let index2 = memberIds.findIndex((id) => id === u2.uid)
        return index1 - index2
      }
      return this._patchAndSortUserInfos(userInfosCloneCopy, groupId, compareFn)
    }
  },

  // clone一下，别影响到好友列表
  getConversationMemberUsrInfos(conversation) {
    let userInfos = []
    if (conversation.type === 0) {
      if (conversation.target !== contactState.selfUserInfo.uid) {
        userInfos.push(wfc.getUserInfo(wfc.getUserId(), false))
      }
      userInfos.push(wfc.getUserInfo(conversation.target, false))
      let userInfosCloneCopy = userInfos.map((u) => Object.assign({}, u))
      userInfos = this._patchAndSortUserInfos(userInfosCloneCopy, '')
    } else if (conversation.type === 1) {
      userInfos = this.getGroupMemberUserInfos(conversation.target, true)
    }
    return userInfos
  },

  getMyFileRecords(beforeUid, count, successCB, failCB) {
    if (!successCB) {
      return
    }
    wfc.getMyFileRecords(
      beforeUid,
      0,
      count,
      (fileRecords) => {
        this._patchFileRecords(fileRecords)
        successCB(fileRecords)
      },
      failCB
    )
  },

  getConversationFileRecords(
    conversation,
    fromUser,
    beforeMessageUid,
    count,
    successCB,
    failCB
  ) {
    wfc.getConversationFileRecords(
      conversation,
      fromUser,
      beforeMessageUid,
      0,
      count,
      (fileRecords) => {
        this._patchFileRecords(fileRecords)
        successCB(fileRecords)
      },
      failCB
    )
  },

  deleteFriend(target) {
    wfc.deleteFriend(
      target,
      () => {
        let conv = new Conversation(ConversationType.Single, target, 0)
        wfc.removeConversation(conv, true)
        conversationState.conversationInfoList =
          conversationState.conversationInfoList.filter(
            (info) => !info.conversation.equal(conv)
          )
      },
      (err) => {
        console.log('deleteFriend error', err)
      }
    )
  },

  _patchFileRecords(fileRecords) {
    fileRecords.forEach((fileRecord) => {
      let groupId =
        fileRecord.conversation.type === 1 ? fileRecord.conversation.target : ''
      if (groupId) {
        fileRecord._userDisplayName = wfc.getGroupMemberDisplayName(
          groupId,
          fileRecord.userId
        )
      } else {
        fileRecord._userDisplayName = wfc.getUserDisplayName(fileRecord.userId)
      }
      let conversationInfo = wfc.getConversationInfo(fileRecord.conversation)
      this._patchConversationInfo(conversationInfo, false)

      if (fileRecord.conversation.type === 0) {
        fileRecord._conversationDisplayName =
          '与' + conversationInfo.conversation._target._displayName + '的聊天'
      } else {
        fileRecord._conversationDisplayName =
          conversationInfo.conversation._target._displayName
      }
      if (fileRecord.name.indexOf(FileMessageContent.FILE_NAME_PREFIX) === 0) {
        fileRecord.name = fileRecord.name.substring(
          fileRecord.name.indexOf(FileMessageContent.FILE_NAME_PREFIX) +
            FileMessageContent.FILE_NAME_PREFIX.length
        )
      }
      fileRecord._timeStr = helper.dateFormat(fileRecord.timestamp)
      fileRecord._sizeStr = helper.humanSize(fileRecord.size)
      fileRecord._fileIconName = helper.getFiletypeIcon(
        fileRecord.name.substring(fileRecord.name.lastIndexOf('.'))
      )
    })
  },

  setPageVisibility(visible) {
    miscState.isPageHidden = !visible
    if (!visible) {
      conversationState.shouldAutoScrollToBottom = false
    } else if (conversationState.currentConversationInfo) {
      conversationState.shouldAutoScrollToBottom = true
    }
  },

  clearConversationUnreadStatus(conversation) {
    let info = wfc.getConversationInfo(conversation)
    if (
      info &&
      info.unreadCount.unread +
        info.unreadCount.unreadMention +
        info.unreadCount.unreadMentionAll >
        0
    ) {
      wfc.clearConversationUnreadStatus(conversation)
      this.updateTray()
    }
  },

  clearAllUnreadStatus() {
    wfc.clearAllUnreadStatus()
    conversationState.conversationInfoList.forEach((info) => {
      info.unreadCount = new UnreadCount()
    })
    this.updateTray()
  },

  notify(msg) {
    let content = msg.messageContent
    let icon = require('@/assets/images/icon.png')
    let tip
    let now = new Date().getTime()
    if (this._lastNotificationTime && now - this._lastNotificationTime < 4000) {
      return
    }
    this._lastNotificationTime = now

    if (msg.direction === 0 /* && !(type===0 && target===file_transfer_id)*/) {
      return
    }
    if (
      MessageConfig.getMessageContentPersitFlag(content.type) ===
      PersistFlag.Persist_And_Count
    ) {
      if (
        msg.status !== MessageStatus.AllMentioned &&
        msg.status !== MessageStatus.Mentioned
      ) {
        let silent = false
        for (const info of conversationState.conversationInfoList) {
          if (info.conversation.equal(msg.conversation)) {
            silent = info.isSilent
            break
          }
        }
        if (silent) {
          return
        }
        tip = '新消息来了'
      } else {
        tip = '有人@你'
      }

      Push.create(tip, {
        body: miscState.enableNotificationMessageDetail
          ? content.digest(msg)
          : '',
        // TODO 下面好像不生效，更新成图片链接
        icon: icon,
        timeout: 4000,
        requireInteraction: true,
        onClick: () => {
          if (isElectron()) {
            ipcRenderer.send(
              IPCEventType.CLICK_NOTIFICATION,
              currentWindow.getMediaSourceId()
            )
          } else {
            window.focus()
            this.close()
          }
          this.setCurrentConversation(msg.conversation)
        },
      }).catch((e) => {
        console.error('Please enable notification')
      })
    }
  },

  updateTray() {
    if (!isElectron() || !miscState.isMainWindow) {
      return
    }
    let count = 0
    conversationState.conversationInfoList.forEach((info) => {
      if (info.isSilent) {
        return
      }
      let unreadCount = info.unreadCount
      count += unreadCount.unread
    })
    if (process.platform === 'linux') {
      this.updateLinuxTitle(count)
      ipcRenderer.send(IPCEventType.UPDATE_BADGE, count)
    } else {
      ipcRenderer.send(IPCEventType.UPDATE_BADGE, count)
    }
  },

  updateLinuxTitle(unreadCount) {
    this.updateLinuxTitle.title = '野火IM'
    this.updateLinuxTitle.unreadCount = unreadCount
    this.updateLinuxTitle.showTitle = true
    if (!miscState.linuxUpdateTitleInterval) {
      miscState.linuxUpdateTitleInterval = setInterval(() => {
        if (
          this.updateLinuxTitle.showTitle ||
          this.updateLinuxTitle.unreadCount < 1
        ) {
          document.title = this.updateLinuxTitle.title
        } else {
          document.title =
            this.updateLinuxTitle.title +
            ' ' +
            this.updateLinuxTitle.unreadCount
        }
        this.updateLinuxTitle.showTitle = !this.updateLinuxTitle.showTitle
      }, 1000)
    }
  },

  updateVoipStatus(conversation, isOngoing) {
    miscState.isVoipOngoing = isOngoing
    if (!conversation) {
      return
    }
    conversationState.conversationInfoList.forEach((ci) => {
      if (ci.conversation.equal(conversation)) {
        ci._isVoipOngoing = isOngoing
      } else {
        ci._isVoipOngoing = false
      }
    })
  },

  _filterForwardMessageContent(message) {
    let content = message.messageContent
    if (content instanceof CallStartMessageContent) {
      content = new TextMessageContent(content.digest(message))
    } else if (content instanceof SoundMessageContent) {
      content = new TextMessageContent(
        content.digest(message) + ' ' + content.duration + "''"
      )
    }
    return content
  },
}

function _reset() {
  conversationState._reset()
  contactState._reset()
  searchState._reset()
  pickState._reset()
  miscState._reset()
}

window.__store = store
window.stringValue = stringValue
window.longValue = longValue
export default store
